/*
 *  Grab II - System sprite grabber
 *  Copyright (C) 2002  Chris Bazley
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public Licence as published by
 *  the Free Software Foundation; either version 2 of the Licence, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public Licence for more details.
 *
 *  You should have received a copy of the GNU General Public Licence
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* ANSI library files */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/* RISC OS library files */
#include "toolbox.h"
#include "event.h"
#include "wimp.h"
#include "wimplib.h"
#include "menu.h"
#include "saveas.h"
#include "window.h"
#include "gadgets.h"
#include "iconbar.h"

/* My library files */
#include "err.h"
#include "msgtrans.h"
#include "hourglass.h"
#include "Macros.h"
#include "sprformats.h"

#define WimpVersion 310

/* Component IDs */
#define MENU_HELP 0x01
#define MENU_QUIT 0x02

/* Gadget IDs */
#define RADIO_ROM_SPRITES  0x00
#define RADIO_RAM_SPRITES  0x01
#define RADIO_TOOL_SPRITES 0x02

/* if defined then ADJUST-click 'Save' button stuff disabled, because SaveAs object (bugged) closes the dialogue box anyway "*/
#define SAVEAS_CRAP

char taskname[32];

static  WimpPollBlock poll_block;
static  IdBlock       id_block;
ObjectId save_id = NULL_ObjectId, underlying_win = NULL_ObjectId;
int save_width=0, save_height=0;
char *ss_file_name = NULL;
ComponentId radio_selected = NULL_ComponentId;

int wimp_version;

/* ----------------------------------------------------------------------- */
/*                       Function prototypes                               */

static WimpMessageHandler wimp_quit_handler;
static ToolboxEventHandler auto_create_handler, error_handler, menu_select_handler, save_handler, iconbar_handler, savebox_reset_dialogue, savebox_abouttobeshown;
#ifndef SAVEAS_CRAP
static ToolboxEventHandler savebox_store_filename;
#endif
static void initialise(void);
static _kernel_oserror *remove_iconised_window(ObjectId id);

/* ----------------------------------------------------------------------- */
/*                         Public functions                                */

int main(int argc, char *argv[])
{
  int event_code;

  initialise();

  /*
   * poll loop
   */
  while (TRUE) 
    event_poll (&event_code, &poll_block, 0);

}

/* ----------------------------------------------------------------------- */
/*                         Private functions                               */

static void initialise(void)
{
  int    toolbox_events = 0,
         wimp_messages = 0;
         
  hourglass_on();

  /*
   * register ourselves with the Toolbox.
   */

  {
    _kernel_oserror *e = toolbox_initialise (0, WimpVersion, &wimp_messages, &toolbox_events, "<Grab2Res$Dir>",msgs_get_descriptor(), &id_block, &wimp_version, 0, 0);
    if(e != NULL) {
      /* limited amount we can do with no messages file... */
      wimp_report_error(e, Wimp_ReportError_Cancel, "Grab 2");
      exit(EXIT_FAILURE);
    }
  }
  err_set_taskname(msgs_lookup("_TaskName"), (wimp_version >= 321));

  /*
   * initialise the event library.
   */

  EF(event_initialise (&id_block));
  EF(event_set_mask (Wimp_Poll_NullMask |
                     Wimp_Poll_PointerLeavingWindowMask |
                     Wimp_Poll_PointerEnteringWindowMask |
                     Wimp_Poll_KeyPressedMask | /* Dealt with by Toolbox */
                     Wimp_Poll_LoseCaretMask |
                     Wimp_Poll_GainCaretMask));

  EF(event_register_toolbox_handler(-1, Toolbox_ObjectAutoCreated, auto_create_handler, 0));
  EF(event_register_toolbox_handler(-1, Toolbox_Error, error_handler, 0));
  EF(event_register_message_handler(Wimp_MQuit, wimp_quit_handler, 0));
  
  hourglass_off();
}

/* ----------------------------------------------------------------------- */

static int wimp_quit_handler(WimpMessage *message,void *handle)
{
  exit(EXIT_SUCCESS);
  return 1; /* claim event */
}

/* ----------------------------------------------------------------------- */

static int auto_create_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  /* Catch auto-created objects and initialise handlers etc. */
  ToolboxObjectAutoCreatedEvent *toace = (ToolboxObjectAutoCreatedEvent *)event;
  
  if (strcmp(toace->template_name, "Menu") == 0) {
    /* Listen for selections */
    EF(event_register_toolbox_handler(id_block->self_id, Menu_Selection, menu_select_handler, NULL));
    return 1; /* claim event */
  }
  
  if (strcmp(toace->template_name, "Iconbar") == 0) {
    /* Listen for selections */
    EF(event_register_toolbox_handler(id_block->self_id, Iconbar_Clicked, iconbar_handler, NULL));
    return 1; /* claim event */
  }
  
  if (strcmp(toace->template_name, "SaveAs") == 0) {
    WimpGetWindowStateBlock winstate;
    
    /* Participate in saving */
    save_id = id_block->self_id;
    EF(event_register_toolbox_handler(save_id, SaveAs_SaveToFile, save_handler, NULL));

    /* Find dimensions of savebox */
    EF(saveas_get_window_id(0, save_id, &underlying_win));
    EF(window_get_wimp_handle(0, underlying_win, &(winstate.window_handle)));
    EF(wimp_get_window_state(&winstate));
    save_width = winstate.visible_area.xmax - winstate.visible_area.xmin;
    save_height = winstate.visible_area.ymax - winstate.visible_area.ymin;

    EF(saveas_set_file_type(0, save_id, FILETYPE_SPRITE));

    EF(event_register_toolbox_handler(save_id, SaveAs_AboutToBeShown, savebox_abouttobeshown, NULL));
    EF(event_register_toolbox_handler(underlying_win, ActionButton_Selected, savebox_reset_dialogue, NULL));
#ifndef SAVEAS_CRAP
    EF(event_register_toolbox_handler(save_id, SaveAs_SaveCompleted, savebox_store_filename, NULL));
#endif    
    /* Read default settings */
    EF(radiobutton_get_state(0, underlying_win, RADIO_ROM_SPRITES, NULL, &radio_selected));
    int len;
    EF(saveas_get_file_name(0, save_id, 0, 0, &len));
    if((ss_file_name = malloc(len+1)) == NULL)
      err_complain_fatal(255, msgs_global("NoMem"));
    EF(saveas_get_file_name(0, save_id, ss_file_name, len, NULL));

    /* Grey out 'toolsprites' option if Window Manager too old */
    if(wimp_version < 321) {
      unsigned int flags_settings;
      EF(gadget_get_flags(0, underlying_win, RADIO_TOOL_SPRITES, &flags_settings));
      EF(gadget_set_flags(0, underlying_win, RADIO_TOOL_SPRITES, flags_settings | Gadget_Faded));
    }

    return 1; /* claim event */
  }
  
  return 0; /* event not handled */
}

/* ----------------------------------------------------------------------- */

static int error_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  ToolboxErrorEvent *totee = (ToolboxErrorEvent *)event;

  if(totee->errnum == 0x80b633 || totee->errnum == 0x131c3) /* "To save drag...", locked file */
    err_report(totee->errnum, totee->errmess);
  else
    err_complain(totee->errnum, totee->errmess);
  return 1; /* claim event */
}

/* ----------------------------------------------------------------------- */

static int menu_select_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  /* Handle click on icon bar menu */
  switch(id_block->self_component) {
    case MENU_HELP:
      /* load help */
      if(_kernel_oscli("Filer_Run Grab2Res:Help") == _kernel_ERROR)
        err_check_rep(_kernel_last_oserror());
      return 1; /* claim event */
      
    case MENU_QUIT:
      /* quit program */
      exit(EXIT_SUCCESS);
      return 1; /* claim event */
  }
  return 0; /* not handled */
}

/* ----------------------------------------------------------------------- */

static int iconbar_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  /* Handle click on icon bar */
  WimpGetPointerInfoBlock pointerinfo;
  WindowShowObjectBlock showblock;
  
  if(FLAG_SET(event->hdr.flags, Iconbar_Clicked_Select)) {
    /* Open window horizontally centred on pointer, above iconbar */
    E_RETV(wimp_get_pointer_info(&pointerinfo), 1);
    showblock.visible_area.xmin = pointerinfo.x - (save_width)/2;
    showblock.visible_area.ymin = 96 + save_height;
    E_RETV(toolbox_show_object(0, save_id, Toolbox_ShowObject_TopLeft, &showblock, id_block->self_id, id_block->self_component), 1);
    RE(remove_iconised_window(underlying_win));
  }
  return 1; /* claim event */
}

/* ----------------------------------------------------------------------- */

int savebox_abouttobeshown(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  /* Dialogue box opening */
  if(ss_file_name != NULL)
    RE(saveas_set_file_name(0, id_block->self_id, ss_file_name));

  RE(radiobutton_set_state(0, underlying_win, radio_selected, 1));
  return 1; /* claim event */
}

/* ----------------------------------------------------------------------- */

int savebox_reset_dialogue(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  ActionButtonSelectedEvent *abse = (ActionButtonSelectedEvent *)event;
  if(id_block->self_component != 0x82bc02 || !FLAG_SET(abse->hdr.flags, ActionButton_Selected_Adjust))
    return 0; /* not interested */

  /* Cancel button clicked on underlying window - reset dialogue box */
  if(ss_file_name != NULL)
    RE(saveas_set_file_name(0, save_id, ss_file_name));

  RE(radiobutton_set_state(0, id_block->self_id, radio_selected, 1));

  return 1; /* claim event */
}

/* ----------------------------------------------------------------------- */

static int save_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  /* Save sprite pool to file, when SaveAs object tells us to */
  SaveAsSaveToFileEvent *save_to_file_block = (SaveAsSaveToFileEvent *)event;
  _kernel_oserror *err;
  int gadget_selected;
  _kernel_swi_regs regs;

  hourglass_on();
    
  /* Which should we save? */
  err = radiobutton_get_state(0, underlying_win, RADIO_ROM_SPRITES, 0, &gadget_selected);
  if(err != NULL)
    goto save_fail_report;

  switch(gadget_selected) {
    case RADIO_ROM_SPRITES:
    case RADIO_RAM_SPRITES:
      {
        /* Get addresses of Wimp sprite pool areas */
        spriteareaheader *rom, *ram;
        err = wimp_base_of_sprites((void **)&rom, (void **)&ram);
        if(err != NULL)
          goto save_fail_report;

        /* Save sprite area */
        regs.r[0] = SPRITEOP_USERAREA_SPRNAME+SPRITEOP_SAVE_AREA;
        if(gadget_selected == RADIO_ROM_SPRITES)
          regs.r[1] = (int)rom;
        else
          regs.r[1] = (int)ram;
        regs.r[2] = (int)save_to_file_block->filename;
        err = _kernel_swi(OS_SpriteOp, &regs, &regs);
        if(err != NULL)
          goto save_fail_report;
      }
      break;
     
    case RADIO_TOOL_SPRITES:
      if(wimp_version >= 321) {
        /* Get address of Wimp toolsprites */
        WimpSysInfo results;
        _kernel_osgbpb_block osgbpb;
        _kernel_osfile_block osfb;
        unsigned int handle;
        spriteareaheader *area_ptr;
        spriteareaheader new_hdr;

        err = wimp_read_sys_info(9, &results);
        if(err != NULL)
          goto save_fail_report;
        area_ptr = (spriteareaheader *)results.r0;

        /* Create empty file with read/write access */
        handle = _kernel_osfind(0x80, save_to_file_block->filename);
        if(handle == _kernel_ERROR)
          goto save_fail_os_error;
        if(handle == 0)
          goto save_fail_silent; /* open failed without error */

        /* Create new sprite area header */
        new_hdr.sprite_count = area_ptr->sprite_count;
        new_hdr.first = sizeof(spriteareaheader);
        new_hdr.used = sizeof(spriteareaheader) + (area_ptr->used - area_ptr->first - 4);

        /* Write replacement header to current pointer */
        osgbpb.dataptr = (void *)((int)&new_hdr + 4); /* (first word omitted in sprite file) */
        osgbpb.nbytes = sizeof(new_hdr) - 4;
        if(_kernel_osgbpb(2, handle, &osgbpb) == _kernel_ERROR)
          goto save_fail_os_error_close;

        /* Write sprite data (skipping 'extension data') to current pointer */
        osgbpb.dataptr = (void *)((int)area_ptr + area_ptr->first);
        osgbpb.nbytes = (int)area_ptr->used - area_ptr->first - 4;
        if(_kernel_osgbpb(2, handle, &osgbpb) == _kernel_ERROR)
          goto save_fail_os_error_close;
        
        /* Close output file */
        if(_kernel_osfind(0x00, (char *)handle) == _kernel_ERROR)
          goto save_fail_os_error;

        /* Set type of named object */
        osfb.load = 0xff9;
        if(_kernel_osfile(18, save_to_file_block->filename, &osfb) == _kernel_ERROR)
          goto save_fail_os_error;

      } else {
        /* Window Manager too old! */
        goto save_fail_silent;
      }
      break;

    default:
      goto save_fail_silent;
  }


  RE(saveas_file_save_completed(1, id_block->self_id, save_to_file_block->filename));
  hourglass_off();
  return 1; /* claim event */


  save_fail_os_error_close:
    _kernel_osfind(0x00, (char *)handle);
  save_fail_os_error:
    err = _kernel_last_oserror();
  save_fail_report:
    err_complain(0, msgs_lookupsub("SaveFail", err->errmess, NULL, NULL, NULL));
  save_fail_silent:
    saveas_file_save_completed(0, id_block->self_id, save_to_file_block->filename);
    hourglass_off();
    return 1; /* claim event */
}

/* ----------------------------------------------------------------------- */

#ifndef SAVEAS_CRAP
int savebox_store_filename(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  /* Successful save */
  SaveAsSaveCompletedEvent *sasce = (SaveAsSaveCompletedEvent *)event;
  int len;

  RE(radiobutton_get_state(0, underlying_win, RADIO_ROM_SPRITES, NULL, &radio_selected));

  if(FLAG_SET(sasce->hdr.flags, SaveAs_DestinationSafe)) {
    /* Use path to destination file */
    len = strlen(sasce->filename);
  }
  else {
    /* Read leafname from writeable field */
    E_RETV(saveas_get_file_name(0, id_block->self_id, NULL, 0, &len), 1);
  }
  free(ss_file_name);
  ss_file_name = malloc(len+1);
  if(ss_file_name == NULL)
    err_complain(255, msgs_global("NoMem"));
  else {
    if(FLAG_SET(sasce->hdr.flags, SaveAs_DestinationSafe))
      strcpy(ss_file_name, sasce->filename);
    else
      RE(saveas_get_file_name(0, id_block->self_id, ss_file_name, len, NULL));
  }

  return 1; /* claim event */
}
#endif    

/* ----------------------------------------------------------------------- */

static _kernel_oserror *remove_iconised_window(ObjectId id)
{
  /* This addresses the problem of iconised windows being re-opened independently by an application, yet remaining on the pinboard */

  /* Get wimp handle of underlying window */    
  WimpMessage msg_block;
  THROW(window_get_wimp_handle(0, id, &msg_block.data.words[0]));

  /* Broadcast message &400CB (window closed) in case it is iconised */
  msg_block.hdr.size = sizeof(msg_block.hdr) + sizeof(int);
  msg_block.hdr.your_ref = 0;
  msg_block.hdr.action_code = Wimp_MWindowClosed;
  return wimp_send_message(Wimp_EUserMessage, &msg_block, 0, 0, NULL);
}
